home *** CD-ROM | disk | FTP | other *** search
/ TeX 1995 July / TeX CD-ROM July 1995 (Disc 1)(Walnut Creek)(1995).ISO / biblio / bibtex / utils / bibclean / fndfil.c < prev    next >
C/C++ Source or Header  |  1992-11-19  |  20KB  |  592 lines

  1. /* -*-C-*- fndfil.c */
  2. /*-->findfile*/
  3. /**********************************************************************/
  4. /****************************** findfile ******************************/
  5. /**********************************************************************/
  6.  
  7. /***********************************************************************
  8. NB:  If  the macro DVI  is defined  when this  file  is compiled, then
  9. additional code will be executed  in isfile() to  optionally trace the
  10. lookup attempts.
  11.  
  12. If the macro TEST is defined at compile-time, a test main program will
  13. be compiled  which takes paths and  names from stdin,  and echos their
  14. existence and expansion to stdout.
  15. ***********************************************************************/
  16.  
  17. /***********************************************************************
  18.  
  19. Search for a file  in a list of  directories specified in the  pathlist,
  20. and if found,  return a pointer  to an internal  buffer (overwritten  on
  21. subsequent  calls)  containing  a   full  filename;  otherwise,   return
  22. (char*)NULL.
  23.  
  24. This version has been extended to use the file cache stored in an
  25. optional hash table pointed to by fsf_table.  If the table exists, and
  26. a match is found there, then it is assumed to be a valid file name and
  27. the operating system is not consulted at all, nor is the path list
  28. examined.
  29.  
  30. Note that this permits finding files whose directories are not
  31. specified in the path list.  You can view this as both a bug and a
  32. feature.  Since the user can control the contents of the FSMAPFILE
  33. file, and can suppress it entirely, the feature outweighs the bug.
  34.  
  35. Filename caching is a valuable economization because file system
  36. lookups are relatively time consuming.  Of course, if the cache is
  37. incorrect, a returned filename may not exist, and a later open attempt
  38. will fail; that should alert to user to update the FSMAPFILE file.
  39.  
  40. The pathlist is expected to contain directories separated by one (or
  41. more) of the characters in the system-dependent SEP_COMP string
  42. constant.  The search is restricted to the directories in the path
  43. list; unlike PC DOS's PATH variable, the current directory is NOT
  44. searched unless it is included in the path list.
  45.  
  46. For example,
  47.  
  48.     findfile(".:/usr/local/lib:/tmp", "foo.bar")
  49.  
  50. will check for the files
  51.  
  52.     ./foo.bar
  53.     /usr/local/lib/foo.bar
  54.     /tmp/foo.bar
  55.  
  56. in that order.  The character which normally separates a directory  from
  57. a file name will  be supplied automatically  if it is  not given in  the
  58. path list.  Thus, the example above could have been written
  59.  
  60.     findfile("./:/usr/local/lib/:/tmp/","foo.bar")
  61.  
  62. Since multiple path separators are ignored, the following are also
  63. acceptable:
  64.  
  65.     findfile("./ : /usr/local/lib/ : /tmp/","foo.bar")
  66.     findfile("./   /usr/local/lib/   /tmp/","foo.bar")
  67.     findfile("./:::::/usr/local/lib/:::::/tmp/:::::","foo.bar")
  68.  
  69. For VAX VMS, additional support is  provided for rooted logical names,
  70. and names  that   look like  rooted  names, but  are not.  Normally, a
  71. logical name  in a path  will look name  "LOGNAME:",  and a  file like
  72. "FILE.EXT"; combining them gives "LOGNAME:FILE.EXT".  A rooted logical
  73. name is created by a DCL command like
  74.  
  75. $ define /translation=(concealed,terminal) LOGNAME DISK:[DIR.]
  76.  
  77. In such a case, VMS requires that what  follows the logical name begin
  78. with a directory, e.g.  the file is "[SUB]FILE.EXT".  Concatenation of
  79. path with name gives "LOGNAME:[SUB]FILE.EXT", which is acceptable.  To
  80. refer   to  a   file in   the   directory   DISK:[DIR],  one    writes
  81. "LOGNAME:[000000]FILE.EXT", where 000000  is the magic  name of a root
  82. directory.
  83.  
  84. If, however, the logical  name looks  like a  rooted name,  but wasn't
  85. defined  with  "/translation=(concealed,terminal)", then VMS  will not
  86. recognize it.   [That  is an easy mistake to  make, and  issuing a DCL
  87. "show logical LOGNAME" command will NOT reveal the error.]  Similarly,
  88. if the logical name  looks like a  directory name, and the file begins
  89. with a directory name, VMS will not recognize that either, even though
  90. there is no ambiquity about what is meant.
  91.  
  92. We therefore make the  following reductions after  an  initial attempt
  93. fails.  First, expand the logical name, skipping further processing if
  94. that fails.  Then, reduce the concatenation as follows:
  95.  
  96.     [DIR.]FILE.EXT          -->     [DIR]FILE.EXT
  97.     [DIR.][SUB]FILE.EXT     -->     [DIR.SUB]FILE.EXT
  98.     [DIR][SUB]FILE.EXT      -->     [DIR.SUB]FILE.EXT
  99.  
  100. If  the logical name  points to a  chain of names,  the standard VMS C
  101. run-time library getenv()  will return  only the first  in  the chain.
  102. Chained logical names were added in VMS version  4.0 without operating
  103. system support  to retrieve all members  of the  chain.   The only way
  104. I've been able to  find the subsequent  members is to spawn a  process
  105. that runs a VMS DCL SHOW LOGICAL command, and then parse its output!
  106.  
  107. Under ATARI TOS, PC DOS, and UNIX,  environment variable  substitution
  108. is supported as follows.  If the initial lookup fails, the filename is
  109. checked for the presence of an initial tilde.   If one is  found, then
  110. if   the  next  character  is   not  alphanumeric or  underscore (e.g.
  111. ~/foo.bar),  the  tilde  is  replaced  by ${HOME};     otherwise (e.g.
  112. ~user/foo.bar), the tilde is stripped and the following name is looked
  113. up using getpwnam() to find the login directory of that user name, and
  114. that  string is substituted  for the ~name.   Then,  the  filename  is
  115. scanned for  $NAME  or ${NAME} sequences,  where NAME conforms  to the
  116. regular expression  [A-Za-z_]+[A-Za-z0-9_]*, and environment  variable
  117. substitution  is performed for  NAME.  Finally,  the   lookup is tried
  118. again.  If it  is successful,  the  name[] string  is replaced by  the
  119. substituted name, so it can be later used to open the file.
  120.  
  121. ***********************************************************************/
  122.  
  123. #include "os.h"
  124. #include "machdefs.h"
  125. #include "unixlib.h"
  126. #include "xctype.h"
  127. #include "xpwd.h"            /* for ~name lookup in envsub() */
  128. #include "xstdlib.h"
  129. #include "xstring.h"
  130.  
  131. #if    OS_PCDOS
  132. #include <io.h>                /* for more function declarations */
  133. #endif /* OS_PCDOS */
  134.  
  135. RCSID("$Id: fndfil.c,v 1.8 1992/10/08 01:42:01 beebe Exp beebe $")
  136.  
  137. /* $Log: fndfil.c,v $
  138.  * Revision 1.8  1992/10/08  01:42:01  beebe
  139.  * Update for C++.
  140.  *
  141.  * Revision 1.7  1992/07/10  14:11:08  beebe
  142.  * Add missing typecast.
  143.  *
  144.  * Revision 1.6  1992/03/11  20:47:50  beebe
  145.  * Change incorrect | to || in preprocessor statements.
  146.  *
  147.  * Revision 1.5  1992/03/10  14:13:53  beebe
  148.  * *** empty log message ***
  149.  *
  150.  * Revision 1.4  1992/02/29  19:42:20  beebe
  151.  * Update for version 3.0.114 [29-Feb-1992] following two-month
  152.  * major overhaul and compilation testing on numerous machines.
  153.  *
  154.  * Revision 1.4  1992/02/29  19:42:20  beebe
  155.  * Update for version 3.0.114 [29-Feb-1992] following two-month
  156.  * major overhaul and compilation testing on numerous machines.
  157.  *
  158.  * Revision 1.3  1992/01/22  02:31:19  beebe
  159.  * Add RCSID() and RCS comment log.
  160.  * */
  161.  
  162. #ifdef DVI
  163. #include "typedefs.h"
  164. #include "hash.h"
  165.  
  166. extern UNSIGN16        debug_code;
  167. extern HASH_TABLE      *fsf_table;
  168. extern BOOLEAN        g_dolog;    /* allow log file creation */
  169. extern FILE        *g_logfp;    /* log file pointer (for errors) */
  170. extern FILE        *stddbg;    /* debug output file pointer */
  171. extern char        g_logname[];     /* name of log file, if created */
  172.  
  173. #if    (OS_ATARI || OS_PCDOS || OS_RMX || OS_TOPS20)
  174. #define NEWLINE(fp) {(void)putc((char)'\r',fp);(void)putc((char)'\n',fp);}
  175.                     /* want <CR><LF> for these systems */
  176. #else  /* NOT (OS_ATARI || OS_PCDOS || OS_RMX || OS_TOPS20) */
  177. #define NEWLINE(fp) (void)putc((char)'\n',fp)    /* want bare <LF> */
  178. #endif /* (OS_ATARI || OS_PCDOS || OS_RMX || OS_TOPS20) */
  179. #endif /* DVI */
  180.  
  181. #define FILE_EXISTS(n) ((int)access((char*)n,0) == 0)
  182.  
  183. #define ISNAMEPREFIX(c) (isalpha(c) || ((c) == '_'))
  184. #define ISNAMESUFFIX(c) (isalnum(c) || ((c) == '_'))
  185.  
  186. #ifdef MIN
  187. #undef MIN
  188. #endif /* MIN */
  189.  
  190. #define    MIN(a,b)    ((a) < (b) ? (a) : (b))
  191.  
  192. #if    OS_VAXVMS
  193. #define GETENV (char*)vms_getenv
  194. #endif /* OS_VAXVMS */
  195.  
  196. /* VMS 4.4 string.h incorrectly declares strspn as char* instead of
  197. size_t, but the return value is (correctly) an integer.  VMS 5.5 string.h
  198. declares it correctly. */
  199. #define STRSPN(s,t) (size_t)strspn(s,t)
  200.  
  201. static const char    *copyname ARGS((char *target_, const char *source_));
  202. static char        *envsub ARGS((const char *filename_));
  203. void            dbglookup ARGS((FILE*, const char*, const char*));
  204. char            *findfile ARGS((const char *pathlist_,
  205.                 const char *name_));
  206. static int        isfile ARGS((char *filename_));
  207.  
  208. #ifdef TEST
  209. int        main ARGS((void));
  210. #endif /* TEST */
  211.  
  212. /***********************************************************************
  213. Copy environment variable or usename, and return updated pointer past
  214. end of copied name in source[].
  215. ***********************************************************************/
  216.  
  217. static const char*
  218. #if STDC
  219. copyname(
  220. register char            *target, /* destination string */
  221. register const char        *source /* source string */
  222. )
  223. #else /* NOT STDC */
  224. copyname(target,source)
  225. register char            *target; /* destination string */
  226. register const char        *source; /* source string */
  227. #endif /* STDC */
  228. {
  229.     if (ISNAMEPREFIX(*source))
  230.     {
  231.     for (*target++ = *source++; ISNAMESUFFIX(*source); )
  232.         *target++ = *source++;    /* copy name */
  233.     *target = '\0';            /* terminate name */
  234.     }
  235.     return (source);
  236. }
  237.  
  238.  
  239. /***********************************************************************
  240. Do  tilde and environment  variable  in  a private copy   of filename,
  241. return  (char*)NULL if no  changes were made,  and otherwise, return a
  242. pointer to the internal copy of the expanded filename.
  243. ***********************************************************************/
  244.  
  245. static char*
  246. #if STDC
  247. envsub(
  248. const char            *filename
  249. )
  250. #else /* NOT STDC */
  251. envsub(filename)
  252. const char            *filename;
  253. #endif /* STDC */
  254. {
  255.  
  256. #if    (OS_ATARI || OS_PCDOS || OS_UNIX)
  257.     static char            altname[MAXPATHLEN+1]; /* result storage */
  258.     register const char        *p;
  259.     register char        *r;
  260.     register const char        *s;
  261.     char            tmpfname[MAXPATHLEN+1];
  262.  
  263. #if    OS_UNIX
  264.     struct passwd        *pw;    /* handle tilde processing */
  265.  
  266.     tmpfname[0] = '\0';            /* initialize tmpfname[] */
  267.     p = strchr(filename,'~');
  268.     if (p != (char*)NULL)        /* handle tilde (once per filename) */
  269.     {
  270.     (void)strncpy(tmpfname,filename,(size_t)(p - filename));
  271.     tmpfname[(int)(p - filename)] = '\0'; /* terminate copied string */
  272.     r = strchr(tmpfname,'\0');    /* remember start of name */
  273.     ++p;                /* point past tilde */
  274.     if (ISNAMEPREFIX(*p))        /* expect ~name */
  275.     {
  276.         p = copyname(r,p);        /* p now points past ~name */
  277.         pw = getpwnam(r);        /* get password structure */
  278.         (void)strcpy(r,pw->pw_dir);    /* replace name by login directory */
  279.     }
  280.     else                /* expect ~/name */
  281.         (void)strcat(tmpfname,"${HOME}"); /* change to ${HOME}/name */
  282.     (void)strcat(tmpfname,p);    /* copy rest of filename */
  283.     }
  284.     else
  285.     (void)strcpy(tmpfname,filename);    /* copy of filename */
  286. #else /* NOT OS_UNIX */
  287.     (void)strcpy(tmpfname,filename);    /* copy of filename */
  288. #endif /* OS_UNIX */
  289.  
  290.     for (r = altname, *r = '\0', p = tmpfname; *p; )
  291.     {                    /* handle environment variables */
  292.     if (*p == '$')            /* expect $NAME or ${NAME} */
  293.     {
  294.         p++;            /* point past $ */
  295.         if (*p == '{')        /* have ${NAME} */
  296.         {
  297.         p = copyname(r,p+1);
  298.         if (*p++ != '}')
  299.             return ((char*)NULL); /* bail out -- no closing brace */
  300.         for (s = (const char *)getenv(r); (s != (char*)NULL) && *s;)
  301.             *r++ = *s++;    /* copy environment variable value */
  302.         *r = '\0';        /* terminate altname[] */
  303.         }
  304.         else if (ISNAMEPREFIX(*p))    /* have $NAME */
  305.         {
  306.         p = copyname(r,p);
  307.         for (s = (const char *)getenv(r); (s != (char*)NULL) && *s;)
  308.             *r++ = *s++;    /* copy environment variable value */
  309.         *r = '\0';        /* terminate altname[] */
  310.         }
  311.         else            /* invalid $NAME */
  312.         *r++ = '$';        /* so just copy bare $ */
  313.     }
  314.     else                /* just copy character */
  315.         *r++ = *p++;
  316.     }
  317.     *r = '\0';                /* terminate altname[] */
  318.  
  319. #ifdef TEST
  320.     printf("envsub: [");
  321.     for (p = filename; *p; ++p)
  322.     {
  323.     putchar(*p);
  324.     if (strchr(SEP_COMP,*p) != (char*)NULL)
  325.         printf("\n\t");
  326.     }
  327.     printf("] ->\n\t[");
  328.     for (p = altname; *p; ++p)
  329.     {
  330.     putchar(*p);
  331.     if (strchr(SEP_COMP,*p) != (char*)NULL)
  332.         printf("\n\t");
  333.     }
  334.     printf("]\n");
  335. #endif /* TEST */
  336.  
  337.     return (altname[0] ? altname : (char*)NULL);
  338. #else  /* NOT (OS_ATARI || OS_PCDOS || OS_UNIX) */
  339.     return ((char*)NULL);        /* no processing to be done */
  340. #endif /* (OS_ATARI || OS_PCDOS || OS_UNIX) */
  341.  
  342. }
  343.  
  344.  
  345. /***********************************************************************
  346. Given a directory search path in pathlist[] and a file name in name[],
  347. search the path  for an  existing file.  If  one  is  found, return  a
  348. pointer  to  an  internal copy   of its  full  name; otherwise, return
  349. (char*)NULL.
  350. ***********************************************************************/
  351.  
  352. char*
  353. #if STDC
  354. findfile(
  355. const char  *pathlist,
  356. const char  *name
  357. )
  358. #else /* NOT STDC */
  359. findfile(pathlist,name)
  360. const char  *pathlist;
  361. const char  *name;
  362. #endif /* STDC */
  363. {
  364.     static char            fullname[MAXPATHLEN+1];
  365.     static char            fullpath[MAXPATHLEN+1];
  366.     int                k;
  367.     int                len;
  368.     register const char        *p;
  369.  
  370.     if ((name == (const char*)NULL) || (*name == '\0'))
  371.     return ((char*)NULL);
  372.  
  373. #ifdef DVI
  374.     HASH_ENTRY            *he;
  375.  
  376.     if (fsf_table != (HASH_TABLE*)NULL)
  377.     {
  378.     he = hash_lookup(name, fsf_table); /* name found in filename cache? */
  379.     if ( (he != (HASH_ENTRY*)NULL) && (he->hash_key != (const char*)NULL) )
  380.      {                /* yes, reconstruct the full filename */
  381.         (void)strcpy(fullname,(const char*)he->hash_data);
  382.         k = strlen(fullname);
  383.         if (strchr(SEP_PATH,fullname[k-1]) == (char*)NULL)
  384.             fullname[k++] = SEP_PATH[0];    /* supply directory separator */
  385.         (void)strncpy(&fullname[k],name,(size_t)(MAXPATHLEN-k));
  386.                     /* append name */
  387.         fullname[MAXPATHLEN] = '\0'; /* strncpy may not supply this */
  388.         dbglookup(stdin, fullname, "CACHED");
  389.         return ((char*)&fullname[0]); /* return fullname without file */
  390.                     /* system lookup */
  391.     }
  392.     dbglookup((FILE*)NULL, name, "NOT CACHED");
  393.     }
  394. #endif /* DVI */
  395.  
  396.  
  397.     /*******************************************************************
  398.       For user convenience, allow path lists to contain nested
  399.       references to environment variables.  We repeatedly expand the
  400.       path list until there are no more changes in it, or a reasonable
  401.       limit is reached (to prevent infinite recursion when a path
  402.       mistakenly contains itself).
  403.     *******************************************************************/
  404.  
  405.     p = pathlist;
  406.     if (pathlist != (char*)NULL)
  407.     {
  408.     /* Copy pathlist[] into fullpath[] cautiously.  On the IBM PC,
  409.     Turbo C 2.0 and 3.0 produce a MAXPATHLEN of 80, which is
  410.     shorter than the longest search path that can be set at DOS
  411.     level in an environment variable.  If environment variables
  412.     are set via initialization files, search paths may be even
  413.     longer.  We intentionally do NOT raise an error if path
  414.     truncation occurs. */
  415.  
  416.     (void)strncpy(fullpath,pathlist,sizeof(fullpath));
  417.     fullpath[sizeof(fullpath)-1] = '\0';
  418.     for (k = 0; k < 10; ++k)
  419.     {
  420.         p = envsub(fullpath);
  421.         if ( (p == (char*)NULL) || (strcmp(p,fullpath) == 0) )
  422.         break;            /* no new substitutions were made */
  423.         else            /* save new expansion */
  424.         (void)strcpy(fullpath,p);
  425.     }
  426.     p = fullpath;            /* remember last expansion */
  427.     }
  428.  
  429.     if ((p == (char*)NULL) || (*p == '\0')) /* no path, try bare filename */
  430.     {
  431.     (void)strncpy(fullname,name,MAXPATHLEN);
  432.     fullname[MAXPATHLEN] = '\0';    /* in case strncpy() doesn't do it */
  433.     return (isfile(fullname) ? (char*)&fullname[0] : (char*)NULL);
  434.     }
  435.     while (*p)                /* process pathlist */
  436.     {
  437.     len = strcspn(p,SEP_COMP);    /* get length before separator */
  438.     len = MIN(MAXPATHLEN-1,len);    /* leave space for possible SEP_PATH */
  439.     (void)strncpy(fullname,p,(size_t)len); /* set directory name */
  440.     k = len;
  441.     if (strchr(SEP_PATH,fullname[k-1]) == (char*)NULL)
  442.         fullname[k++] = SEP_PATH[0];    /* supply directory separator */
  443.     (void)strncpy(&fullname[k],name,(size_t)(MAXPATHLEN-k));
  444.                     /* append name */
  445.     fullname[MAXPATHLEN] = '\0';    /* strncpy may not supply this */
  446.     if (isfile(fullname))
  447.         return ((char*)&fullname[0]);
  448.  
  449. #if    OS_VAXVMS
  450.     do                /* single trip loop */
  451.     {
  452.         char        *logname;
  453.         int            n;
  454.  
  455.         if (fullname[k-1] == ']')    /* then not logical name */
  456.         break;            /* here's a loop exit */
  457.         fullname[k] = '\0';        /* terminate logical name */
  458.         logname = GETENV(fullname);
  459.         if (logname == (char*)NULL)    /* then unknown logical name */
  460.         break;            /* here's a loop exit */
  461.         (void)strcpy(fullname,logname);
  462.         n = strlen(fullname);
  463.         if ( (n >= 2) && (fullname[n-2] == '.') && (fullname[n-1] == ']') )
  464.         {                /* have rooted name [dir.] */
  465.         if (name[0] == '[')    /* have [dir.][sub]name */
  466.             (void)strcpy(&fullname[n-1],&name[1]); /* [dir.sub]name */
  467.         else            /* have [dir.]name */
  468.         {
  469.             fullname[n-2] = ']';
  470.             (void)strcpy(&fullname[n-1],&name[0]); /* [dir]name */
  471.         }
  472.         }
  473.         else if (fullname[n-1] == ']')
  474.         {                /* have normal name [dir] */
  475.         if (name[0] == '[')    /* have [dir][sub]name */
  476.         {
  477.             fullname[n-1] = '.';
  478.             (void)strcpy(&fullname[n],&name[1]); /* [dir.sub]name */
  479.         }
  480.         else            /* have [dir]name */
  481.             (void)strcpy(&fullname[n],&name[0]); /* [dir]name */
  482.         }
  483.         else            /* must be logical name component */
  484.         {
  485.         if (fullname[n-1] != ':')
  486.             fullname[n++] = ':';
  487.         (void)strcpy(&fullname[n],&name[0]); /* logname:name */
  488.         }
  489.         if (isfile(fullname))
  490.         return ((char*)&fullname[0]);
  491.     }
  492.     while (0);
  493. #endif /* OS_VAXVMS */
  494.  
  495.     p += len;            /* point past first path */
  496.     if (*p)                /* then not at end of path list */
  497.     {
  498.         p++;            /* point past separator */
  499.         p += STRSPN(p,SEP_COMP);    /* skip over any extra separators */
  500.     }
  501.     }
  502.     return ((char*)NULL);        /* no file found */
  503. }
  504.  
  505.  
  506. /***********************************************************************
  507. Return  a non-zero   result if a  file  named   filename  exists,  and
  508. otherwise, return zero.  If the first existence check  fails,  then we
  509. try tilde and  environment  variable substitutions, and  if there were
  510. any, a second existence check  is  made.  If this second one succeeds,
  511. we  replace filename with the  substituted  name,  since  that will be
  512. needed later to open the file.
  513. ***********************************************************************/
  514.  
  515. static int
  516. #if STDC
  517. isfile(
  518. char                *filename
  519. )
  520. #else /* NOT STDC */
  521. isfile(filename)
  522. char                *filename;
  523. #endif /* STDC */
  524. {
  525.     int                exists;
  526.     char            *p;
  527.  
  528.     exists = FILE_EXISTS(filename);
  529.  
  530.     if (!exists)        /* try environment variable substitution */
  531.     {
  532.     p = envsub(filename);
  533.     if (p != (char*)NULL)
  534.     {
  535.         exists = FILE_EXISTS(p);
  536.         if (exists)
  537.         (void)strcpy(filename,p);
  538.     }
  539.     }
  540.  
  541. #ifdef DVI
  542.     if (exists)
  543.     dbglookup(stdin, filename, "OK");
  544.     else
  545.     dbglookup((FILE*)NULL, filename, "FAILED");
  546. #endif /* DVI */
  547.  
  548.     return (exists);
  549. }
  550.  
  551. #ifdef TEST
  552. /***********************************************************************
  553. Simple test program for findfile().  It prompts for a single directory
  554. search path, then  loops reading test filenames,  uses findfile to see
  555. if they exist in the search path, and  prints the name of the matching
  556. file,  if  any.  If tilde  or environment  variable  substitutions are
  557. made, they are printed as well.
  558. ***********************************************************************/
  559.  
  560. int
  561. main(VOID_ARG)
  562. {
  563.     char            pathlist[MAXPATHLEN+1];
  564.     char            name[MAXPATHLEN+1];
  565.     char            *p;
  566.  
  567.     (void)printf("Enter file search path: ");
  568.     (void)fgets(pathlist,MAXPATHLEN,stdin);
  569.  
  570.     p = strchr(pathlist,'\n');
  571.     if (p != (char*)NULL)
  572.     *p = '\0';            /* kill terminal newline */
  573.  
  574.     for (;;)
  575.     {
  576.     (void)printf("Enter file name: ");
  577.     if (fgets(name,MAXPATHLEN,stdin) == (char*)NULL)
  578.         break;            /* here's the loop exit */
  579.     p = strchr(name,'\n');
  580.     if (p != (char*)NULL)
  581.         *p = '\0';            /* kill terminal newline */
  582.     p = findfile(pathlist,name);
  583.     if (p == (char*)NULL)
  584.         (void)printf("\tNo such file\n");
  585.     else
  586.         (void)printf("\tFile is [%s]\n",p);
  587.     }
  588.     exit(EXIT_SUCCESS);
  589.     return (0);
  590. }
  591. #endif /* TEST */
  592.